/*! \file
 * \brief Enter a brief description here.
 *
 * Enter a detailed description here. (optional)
 * \author Author (Ivo Wingelaar)
 * \date Date (2011)
 * \version Version (0.0.1)
 * \copyright Copyright (GNU LGPL v3)
 */

#ifndef __LYNX_VIDEO_MESH_HPP_INCLUDED__
#define __LYNX_VIDEO_MESH_HPP_INCLUDED__

#include "LynxMaterial.hpp"
#include "LynxVertex.hpp"

namespace Lynx
{

enum E_BUFFER_TYPE
{
    //! Does not change anything
    EBT_NONE=0,
    //! Change the vertex mapping
    EBT_VERTEX,
    //! Change the index mapping
    EBT_INDEX,
    //! Change both vertex and index mapping to the same value
    EBT_VERTEX_AND_INDEX
};

enum E_HARDWARE_MAPPING
{
    //! Don't store on the hardware
    EHM_NEVER=0,
    //! Rarely changed, usually stored completely on the hardware
    EHM_STATIC,
    //! Sometimes changed, driver optimized placement
    EHM_DYNAMIC,
    //! Always changed, cache optimizing on the GPU
    EHM_STREAM
};

enum E_INDEX_TYPE
{
    EIT_16BIT = 0,
    EIT_32BIT
};

class MeshBuffer : public virtual Myth::ReferenceCounter
{
public:
    virtual Material& get_material() = 0;

    virtual const Material& get_material() const = 0;

    virtual E_VERTEX_TYPE get_vertex_type() const = 0;

    virtual void* get_vertices() = 0;

    virtual const void* get_vertices() const = 0;

    virtual uint32_t get_vertex_count() const = 0;

    virtual E_INDEX_TYPE get_index_type() const =0;

    virtual uint16_t* get_indices() = 0;

    virtual const uint16_t* get_indices() const = 0;

    virtual uint32_t get_index_count() const = 0;

    virtual const Myth::Math::BoundingBox3f& get_bounding_box() const = 0;

    virtual void set_bounding_box(const Myth::Math::BoundingBox3f& box) = 0;

    virtual void recalculate_bounding_box() = 0;

    virtual const Myth::Math::Vector3f& get_position(uint32_t i) const = 0;

    virtual Myth::Math::Vector3f& get_position(uint32_t i) = 0;

    virtual const Myth::Math::Vector3f& get_normal(uint32_t i) const = 0;

    virtual Myth::Math::Vector3f& get_normal(uint32_t i) = 0;

    virtual const Myth::Math::Vector2f& get_texture_coords(uint32_t i) const = 0;

    virtual Myth::Math::Vector2f& get_texture_coords(uint32_t i) = 0;

    virtual void append(const void* const vertices, uint32_t numVertices, const uint16_t* const indices, uint32_t numIndices) = 0;

    virtual E_HARDWARE_MAPPING get_vertex_hardware_mapping_hint() const = 0;

    virtual E_HARDWARE_MAPPING get_index_hardware_mapping_hint() const = 0;

    virtual void set_hardware_mapping_hint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0;

    virtual void set_dirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX) = 0;

    virtual uint32_t get_vertex_changed_id() const = 0;

    virtual uint32_t get_index_changed_id() const = 0;
};

template <class T>
class MeshBufferC : public MeshBuffer
{
public:
    MeshBufferC() :
        mChangedID_Vertex(1),
        mChangedID_Index(1),
        mMappingHint_Vertex(EHM_NEVER),
        mMappingHint_Index(EHM_NEVER)
    {
        //
    }

    virtual const Material& get_material() const
    {
        return mMaterial;
    }

    virtual Material& get_material()
    {
        return mMaterial;
    }

    virtual const void* get_vertices() const
    {
        return &(mVertices[0]);
    }

    virtual void* get_vertices()
    {
        return &(mVertices[0]);
    }

    virtual uint32_t get_vertex_count() const
    {
        return mVertices.size();
    }

    virtual E_INDEX_TYPE get_index_type() const
    {
        return EIT_16BIT;
    }

    virtual const uint16_t* get_indices() const
    {
        return &(mIndices[0]);
    }

    virtual uint16_t* get_indices()
    {
        return &(mIndices[0]);
    }

    virtual uint32_t get_index_count() const
    {
        return mIndices.size();
    }

    virtual const Myth::Math::BoundingBox3f& get_bounding_box() const
    {
        return mBoundingBox;
    }

    virtual void set_bounding_box(const Myth::Math::BoundingBox3f& box)
    {
        mBoundingBox = box;
    }

    virtual void recalculate_bounding_box()
    {
        if(mVertices.empty())
            mBoundingBox.reset(0,0,0);
        else
        {
            mBoundingBox.reset(mVertices[0].mPos);
            for (uint32_t i=1; i<mVertices.size(); ++i)
                mBoundingBox.addInternalPoint(mVertices[i].mPos);
        }
    }

    virtual E_VERTEX_TYPE get_vertex_type() const
    {
        return T().get_type();
    }

    virtual const Myth::Math::Vector3f& get_position(uint32_t i) const
    {
        return mVertices[i].mPos;
    }

    virtual Myth::Math::Vector3f& get_position(uint32_t i)
    {
        return mVertices[i].mPos;
    }

    virtual const Myth::Math::Vector3f& get_normal(uint32_t i) const
    {
        return mVertices[i].mNormal;
    }

    virtual Myth::Math::Vector3f& get_normal(uint32_t i)
    {
        return mVertices[i].mNormal;
    }

    virtual const Myth::Math::Vector2f& get_texture_coords(uint32_t i) const
    {
        return mVertices[i].mTCoords;
    }

    virtual Myth::Math::Vector2f& get_texture_coords(uint32_t i)
    {
        return mVertices[i].mTCoords;
    }

    virtual void append(const void* const vertices, uint32_t numVertices, const uint16_t* const indices, uint32_t numIndices)
    {
        if (vertices == get_vertices())
            return;

        const uint32_t vertexCount = get_vertex_count();
        uint32_t i;

        mVertices.reserve(vertexCount+numVertices);
        for (i=0; i<numVertices; ++i)
        {
            mVertices.push_back(reinterpret_cast<const T*>(vertices)[i]);
            mBoundingBox.addInternalPoint(reinterpret_cast<const T*>(vertices)[i].mPos);
        }

        mIndices.reserve(get_index_count()+numIndices);
        for(i=0; i<numIndices; ++i)
        {
            mIndices.push_back(indices[i]+vertexCount);
        }
    }

    virtual E_HARDWARE_MAPPING get_vertex_hardware_mapping_hint() const
    {
        return mMappingHint_Vertex;
    }

    virtual E_HARDWARE_MAPPING get_index_hardware_mapping_hint() const
    {
        return mMappingHint_Index;
    }

    virtual void set_hardware_mapping_hint(E_HARDWARE_MAPPING NewMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX )
    {
        if (buffer==EBT_VERTEX_AND_INDEX || buffer==EBT_VERTEX)
            mMappingHint_Vertex=NewMappingHint;
        if (buffer==EBT_VERTEX_AND_INDEX || buffer==EBT_INDEX)
            mMappingHint_Index=NewMappingHint;
    }

    virtual void set_dirty(E_BUFFER_TYPE buffer=EBT_VERTEX_AND_INDEX)
    {
        if (buffer==EBT_VERTEX_AND_INDEX ||buffer==EBT_VERTEX)
            ++mChangedID_Vertex;
        if (buffer==EBT_VERTEX_AND_INDEX || buffer==EBT_INDEX)
            ++mChangedID_Index;
    }

    virtual uint32_t get_vertex_changed_id() const {return mChangedID_Vertex;}

    virtual uint32_t get_index_changed_id() const {return mChangedID_Index;}

    uint32_t mChangedID_Vertex;
    uint32_t mChangedID_Index;

    E_HARDWARE_MAPPING mMappingHint_Vertex;
    E_HARDWARE_MAPPING mMappingHint_Index;

    Material mMaterial;
    std::vector<T> mVertices;
    std::vector<uint16_t> mIndices;
    Myth::Math::BoundingBox3f mBoundingBox;
};

//! Standard meshbuffer
typedef MeshBufferC<Vertex> MeshBufferVertex;
//! Meshbuffer with two texture coords per vertex, e.g. for lightmaps
typedef MeshBufferC<Vertex2TCoords> MeshBufferVertex2TCoords;
//! Meshbuffer with vertices having tangents stored, e.g. for normal mapping
typedef MeshBufferC<VertexTangents> MeshBufferVertexTangents;

/** class **/
/** Mesh  **/
//! A class containing the static geometry of an object.
class Mesh : public virtual Myth::ReferenceCounter
{
public:
    Mesh()
    {
        //
    }

    virtual ~Mesh()
    {
        for(uint32_t a = 0; a < mMeshBuffers.size(); a++)
            mMeshBuffers[a]->decrement_counter();
    }

    virtual uint32_t get_mesh_buffer_count() const
    {
        return mMeshBuffers.size();
    }

    virtual MeshBuffer* get_mesh_buffer(uint32_t nr) const
    {
        return mMeshBuffers[nr];
    }

    virtual MeshBuffer* get_mesh_buffer(const Material material) const
    {
        for(int32_t a = (int)mMeshBuffers.size()-1; a >= 0; a++)
        {
            if(material == mMeshBuffers[a]->get_material())
                return mMeshBuffers[a];
        }

        return 0;
    }

    virtual void add_mesh_buffer(MeshBuffer* buf)
    {
        if(buf)
        {
            buf->increment_counter();
            mMeshBuffers.push_back(buf);
        }
    }

    virtual const Myth::Math::BoundingBox3f& get_bounding_box() const
    {
        return mBoundingBox;
    }

    virtual void set_bounding_box(const Myth::Math::BoundingBox3f& box)
    {
        mBoundingBox = box;
    }

    virtual void recalculate_bounding_box()
    {
        if(mMeshBuffers.size())
        {
            mBoundingBox = mMeshBuffers[0]->get_bounding_box();
            for(uint32_t a = 1; a < mMeshBuffers.size(); a++)
                mBoundingBox.addInternalBox(mMeshBuffers[a]->get_bounding_box());
        }
        else
            mBoundingBox.reset(0.0f, 0.0f, 0.0f);
    }

    virtual void set_material_flag(E_MATERIAL_FLAG flag, bool new_value)
    {
        for(uint32_t a = 0; a < mMeshBuffers.size(); a++)
            mMeshBuffers[a]->get_material().set_flag(flag, new_value);
    }

    virtual void set_hardware_mapping_hint(E_HARDWARE_MAPPING newMappingHint, E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX)
    {
        for(uint32_t a = 0; a < mMeshBuffers.size(); a++)
            mMeshBuffers[a]->set_hardware_mapping_hint(newMappingHint, buffer);
    }

    virtual void set_dirty(E_BUFFER_TYPE buffer = EBT_VERTEX_AND_INDEX)
    {
        for(uint32_t a = 0; a < mMeshBuffers.size(); a++)
            mMeshBuffers[a]->set_dirty(buffer);
    }

    std::vector<MeshBuffer*> mMeshBuffers;
    Myth::Math::BoundingBox3f mBoundingBox;
};

/** class        **/
/** AnimatedMesh **/
//! A class containing the animated geometry of an object.
class AnimatedMesh : public virtual Myth::ReferenceCounter
{
public:
    /** const function  **/
    /** get_frame_count **/
    //! Returns the total amount of frames.
    /// \return uint32_t
    ///         The total amount of frames.
    /// \param  void
    virtual uint32_t get_frame_count(void) const = 0;

    /** const function **/
    /** get_frame      **/
    //! Returns the mesh corresponding with the frame-number.
    /// \return Mesh*
    ///         The mesh corresponding with the frame-number.
    /// \param  uint32_t
    ///         A zero-based frame index, get_frame_count()-1 is the maximum.
    virtual Mesh* get_frame(uint32_t frame) const = 0;
};

} // namespace Lynx

#endif // __LYNX_VIDEO_MESH_HPP_INCLUDED__

